import sys
import serial
import serial.tools.list_ports
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                               QHBoxLayout, QGridLayout, QLabel, QComboBox, 
                               QPushButton, QTextEdit, QLineEdit, QFileDialog,
                               QGroupBox, QFrame, QSpacerItem, QSizePolicy, QScrollBar, QProgressBar,
                               QTabWidget, QTreeWidget, QTreeWidgetItem, QCheckBox, QListWidget,
                               QDialog, QFormLayout, QSpinBox, QDialogButtonBox)
from PySide6.QtCore import QThread, Signal, QTimer, Qt
from PySide6.QtGui import QFont, QTextCursor, QTextCharFormat, QColor, QIcon
import time
import os
import qtawesome as qta

# 导入协议相关模块
from protocol_manager import ProtocolManager, DynamicProtocolWidget
# 导入HEX文件解析器
from hex_parser import HexParser, is_hex_file
# 导入数据帧解析器
from frame_parser import FrameParser, get_available_configs


class ParserInstanceWidget(QWidget):
    """单个协议解析实例的UI组件"""
    def __init__(self, frame_parsers, parent=None):
        super().__init__(parent)
        self.frame_parsers = frame_parsers
        self.current_parser = None
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        layout.setContentsMargins(5, 5, 5, 5)

        # 顶部控制栏
        controls_layout = QHBoxLayout()
        
        # 解析协议选择
        controls_layout.addWidget(QLabel("解析协议:"))
        self.protocol_combo = QComboBox()
        if self.frame_parsers:
            self.protocol_combo.addItems(self.frame_parsers.keys())
            self.on_parser_selected(self.protocol_combo.currentText()) # 初始化
        else:
            self.protocol_combo.addItem("无可用协议")
            self.protocol_combo.setEnabled(False)
        self.protocol_combo.currentTextChanged.connect(self.on_parser_selected)
        controls_layout.addWidget(self.protocol_combo, 1)

        # 数据方向选择
        controls_layout.addWidget(QLabel("数据方向:"))
        self.direction_combo = QComboBox()
        self.direction_combo.addItems(["解析接收数据", "解析发送数据"])
        controls_layout.addWidget(self.direction_combo, 1)

        layout.addLayout(controls_layout)

        # 解析结果显示
        self.results_tree = QTreeWidget()
        self.results_tree.setHeaderLabels(["项目", "值"])
        self.results_tree.setColumnWidth(0, 150)
        layout.addWidget(self.results_tree)

    def on_parser_selected(self, config_name):
        """当用户选择一个新的解析协议时调用"""
        if config_name in self.frame_parsers:
            self.current_parser = self.frame_parsers[config_name]
            # 更新父tab的标题
            # 使用 indexOf(self) 来确保更新正确的标签页
            parent_widget = self.parentWidget()
            if parent_widget and isinstance(parent_widget.parentWidget(), QTabWidget):
                parent_tab_widget = parent_widget.parentWidget()
                current_index = parent_tab_widget.indexOf(self)
                if current_index != -1:
                    parent_tab_widget.setTabText(current_index, config_name)
        else:
            self.current_parser = None

    def get_selected_parser(self):
        return self.current_parser

    def get_direction(self):
        return self.direction_combo.currentText()

    def update_parser_tree(self, result_dict, timestamp, raw_frame_hex, direction):
        """更新解析结果树，保留历史记录，并高亮显示验证结果"""
        
        # 折叠所有现有的顶层项目
        for i in range(self.results_tree.topLevelItemCount()):
            self.results_tree.topLevelItem(i).setExpanded(False)

        # 创建新的顶层项目
        record_item = QTreeWidgetItem()
        record_item.setText(0, f"[{timestamp}] {direction}")
        
        # 在顶部插入新项目
        self.results_tree.insertTopLevelItem(0, record_item)
        
        # 添加原始数据
        QTreeWidgetItem(record_item, ["Raw Data (Hex)", raw_frame_hex])

        # 处理解析结果
        for key, value in result_dict.items():
            # 对 'parsed_data' 进行特殊处理，创建可折叠的子树
            if key == 'parsed_data' and isinstance(value, dict):
                parent_item = QTreeWidgetItem(record_item, [str(key)])
                for sub_key, sub_value in value.items():
                    QTreeWidgetItem(parent_item, [str(sub_key), str(sub_value)])
                parent_item.setExpanded(True) # 展开parsed_data
            else:
                # 其他字段正常显示
                item = QTreeWidgetItem(record_item, [str(key), str(value)])
                
                # 高亮显示校验结果
                if key == 'validation':
                    validation_text = str(value)
                    if "成功" in validation_text:
                        item.setForeground(1, QColor("lightgreen"))
                    elif "失败" in validation_text:
                        item.setForeground(1, QColor("salmon"))
        
        # 展开新的顶层项目
        record_item.setExpanded(True)
        
        # 限制历史记录数量
        while self.results_tree.topLevelItemCount() > 50:
            self.results_tree.takeTopLevelItem(self.results_tree.topLevelItemCount() - 1)

class SerialThread(QThread):
    """串口数据接收线程 - 支持基于波特率的超时分包"""
    data_received = Signal(bytes)
    
    def __init__(self, serial_port):
        super().__init__()
        self.serial_port = serial_port
        self.running = False
        
        # 数据包缓冲区和超时相关
        self.packet_buffer = bytearray()
        self.last_receive_time = 0
        self.packet_timeout = self._calculate_packet_timeout()
        
        # 调试信息
        print(f"DEBUG: 串口接收线程初始化 - 数据包超时: {self.packet_timeout:.2f}ms")
    
    def _calculate_packet_timeout(self):
        """
        计算数据包超时时间（基于波特率）
        超时时间 = 2个字符的传输时间
        """
        try:
            # 获取串口参数
            baudrate = self.serial_port.baudrate
            bytesize = self.serial_port.bytesize
            stopbits = self.serial_port.stopbits
            parity = self.serial_port.parity
            
            # 计算每个字符的位数
            bits_per_char = 1  # 起始位
            bits_per_char += bytesize  # 数据位
            
            # 停止位
            if stopbits == 1:
                bits_per_char += 1
            elif stopbits == 1.5:
                bits_per_char += 1.5
            elif stopbits == 2:
                bits_per_char += 2
                
            # 校验位
            if parity != 'N':  # 如果有校验位
                bits_per_char += 1
            
            # 计算单个字符传输时间（毫秒）
            char_time_ms = (bits_per_char * 1000.0) / baudrate
            
            # 返回2个字符的传输时间作为超时
            timeout_ms = char_time_ms * 2
            
            # 最小超时时间为5ms，最大为100ms
            timeout_ms = max(5.0, min(100.0, timeout_ms))
            
            print(f"DEBUG: 串口参数 - 波特率:{baudrate}, 数据位:{bytesize}, 停止位:{stopbits}, 校验位:{parity}")
            print(f"DEBUG: 计算结果 - 每字符:{bits_per_char}位, 字符时间:{char_time_ms:.2f}ms, 包超时:{timeout_ms:.2f}ms")
            
            return timeout_ms
            
        except Exception as e:
            print(f"DEBUG: 计算超时时间失败: {e}, 使用默认值20ms")
            return 20.0  # 默认20ms超时
    
    def run(self):
        self.running = True
        self.packet_buffer.clear()
        self.last_receive_time = 0
        
        print(f"DEBUG: 串口接收线程开始运行，超时分包启用")
        
        while self.running and self.serial_port.is_open:
            try:
                current_time = time.time() * 1000  # 转换为毫秒
                
                # 检查是否有新数据
                if self.serial_port.in_waiting > 0:
                    # 读取新数据
                    new_data = self.serial_port.read(self.serial_port.in_waiting)
                    self.packet_buffer.extend(new_data)
                    self.last_receive_time = current_time
                    
                    # 调试输出
                    if len(new_data) > 0:
                        hex_str = " ".join([f"{b:02X}" for b in new_data])
                        print(f"DEBUG: 接收到 {len(new_data)} 字节: {hex_str}")
                
                # 检查超时
                elif len(self.packet_buffer) > 0 and self.last_receive_time > 0:
                    time_since_last_receive = current_time - self.last_receive_time
                    
                    if time_since_last_receive >= self.packet_timeout:
                        # 超时，发送完整数据包
                        packet_data = bytes(self.packet_buffer)
                        hex_str = " ".join([f"{b:02X}" for b in packet_data])
                        print(f"DEBUG: 超时分包 - 发送数据包 {len(packet_data)} 字节: {hex_str} (超时:{time_since_last_receive:.2f}ms)")
                        
                        self.data_received.emit(packet_data)
                        self.packet_buffer.clear()
                        self.last_receive_time = 0
                
                # 短暂休眠，避免CPU占用过高
                self.msleep(1)  # 1ms延时，提高响应性
                
            except Exception as e:
                print(f"串口接收错误: {e}")
                break
    
    def stop(self):
        """停止接收线程"""
        self.running = False
        
        # 如果缓冲区还有数据，发送出去
        if len(self.packet_buffer) > 0:
            packet_data = bytes(self.packet_buffer)
            print(f"DEBUG: 线程停止 - 发送剩余数据包 {len(packet_data)} 字节")
            self.data_received.emit(packet_data)
            self.packet_buffer.clear()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.serial_port = None
        self.serial_thread = None
        
        # 初始化协议管理器
        self.protocol_manager = ProtocolManager()
        self.current_protocol = None
        self.protocol_manager.status_update.connect(self.handle_protocol_status)
        self.protocol_manager.progress_update.connect(self.handle_protocol_progress)
        
        # 初始化HEX解析器
        self.hex_parser = HexParser()
        self.current_file_data = None  # 当前文件的二进制数据
        
        # 初始化数据帧解析器相关
        self.frame_parsers = {cfg['frame_name']: FrameParser(cfg) for cfg in get_available_configs()}
        
        self.init_ui()
        self.apply_dark_theme()
        
        # 设置窗口启动时默认最大化
        self.showMaximized()
        
    def init_ui(self):
        """初始化用户界面"""
        self.setWindowTitle("MCU OTA升级工具")
        self.setGeometry(100, 100, 1400, 900)
        
        # 创建中央窗口部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局 - 使用网格布局
        main_layout = QGridLayout(central_widget)
        main_layout.setSpacing(10)
        
        # 左上角：通讯接口设置
        serial_settings_panel = self.create_serial_settings_panel()
        main_layout.addWidget(serial_settings_panel, 0, 0, 1, 1)
        
        # 右上角：通讯数据显示区域
        data_display_panel = self.create_data_display_panel()
        main_layout.addWidget(data_display_panel, 0, 1, 1, 2)
        
        # 左下角：手动发送数据
        manual_send_panel = self.create_manual_send_panel()
        main_layout.addWidget(manual_send_panel, 1, 0, 1, 1)
        
        # 右下角：功能区（OTA 和 数据解析）
        tabs_panel = self.create_ota_and_parser_tabs()
        main_layout.addWidget(tabs_panel, 1, 1, 1, 2)
        
        # 设置行和列的拉伸因子
        main_layout.setRowStretch(0, 5)  # 上方占5/6
        main_layout.setRowStretch(1, 1)  # 下方占1/6
        main_layout.setColumnStretch(0, 1) # 左侧占1/4
        main_layout.setColumnStretch(1, 2) # 右侧占2/4
        main_layout.setColumnStretch(2, 1) # 最右侧占1/4
        
    def create_ota_and_parser_tabs(self):
        """创建包含OTA和数据解析的Tab窗口"""
        tabs = QTabWidget()
        
        # OTA 标签页
        ota_widget = QWidget()
        ota_layout = QHBoxLayout(ota_widget)
        ota_layout.setContentsMargins(0, 5, 0, 0) # 调整边距
        
        file_selection_panel = self.create_file_selection_panel()
        protocol_config_panel = self.create_protocol_config_panel()
        
        ota_layout.addWidget(file_selection_panel, 1)
        ota_layout.addWidget(protocol_config_panel, 1)
        
        tabs.addTab(ota_widget, "OTA升级")
        
        # 数据解析 标签页
        parser_container = self.create_frame_parser_container()
        tabs.addTab(parser_container, "数据解析")
        
        # 设备控制 标签页
        device_control_widget = self.create_device_control_widget()
        tabs.addTab(device_control_widget, "设备控制")
        
        # 添加占位符以保持布局
        ota_layout.addStretch()
        
        return tabs
        
    def create_serial_settings_panel(self):
        """创建通讯接口设置面板"""
        panel = QFrame()
        panel.setFrameStyle(QFrame.Box)
        layout = QVBoxLayout(panel)
        
        # 面板标题
        title = QLabel("通讯接口设置")
        title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title)
        
        # 串口设置组
        serial_group = self.create_serial_settings_group()
        layout.addWidget(serial_group)
        
        # 添加弹性空间
        layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        
        return panel
        
    def create_serial_settings_group(self):
        """创建串口设置组"""
        group = QGroupBox("串口设置")
        layout = QGridLayout(group)
        
        # 端口选择
        layout.addWidget(QLabel("端口:"), 0, 0)
        self.port_combo = QComboBox()
        self.refresh_ports()
        layout.addWidget(self.port_combo, 0, 1)
        
        refresh_btn = QPushButton("刷新")
        refresh_btn.clicked.connect(self.refresh_ports)
        layout.addWidget(refresh_btn, 0, 2)
        
        # 波特率 - 设置为可编辑，支持直接输入自定义值
        layout.addWidget(QLabel("波特率:"), 1, 0)
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200", "230400", "460800", "921600"])
        self.baudrate_combo.setCurrentText("115200")
        # 设置为可编辑模式，允许用户直接输入自定义波特率
        self.baudrate_combo.setEditable(True)
        self.baudrate_combo.setInsertPolicy(QComboBox.NoInsert)  # 不自动插入到列表中
        layout.addWidget(self.baudrate_combo, 1, 1)  # 占用两列
        
        # 数据位
        layout.addWidget(QLabel("数据位:"), 2, 0)
        self.databits_combo = QComboBox()
        self.databits_combo.addItems(["5", "6", "7", "8"])
        self.databits_combo.setCurrentText("8")
        layout.addWidget(self.databits_combo, 2, 1)
        
        # 停止位
        layout.addWidget(QLabel("停止位:"), 3, 0)
        self.stopbits_combo = QComboBox()
        self.stopbits_combo.addItems(["1", "1.5", "2"])
        self.stopbits_combo.setCurrentText("1")
        layout.addWidget(self.stopbits_combo, 3, 1)
        
        # 校验位
        layout.addWidget(QLabel("校验位:"), 4, 0)
        self.parity_combo = QComboBox()
        self.parity_combo.addItems(["None", "Even", "Odd", "Mark", "Space"])
        self.parity_combo.setCurrentText("None")
        layout.addWidget(self.parity_combo, 4, 1)
        
        # 发送格式
        layout.addWidget(QLabel("发送格式:"), 5, 0)
        self.send_format_combo = QComboBox()
        self.send_format_combo.addItems(["文本", "Hex"])
        self.send_format_combo.setCurrentText("Hex")  # 默认为Hex
        self.send_format_combo.currentTextChanged.connect(self.on_send_format_changed)
        layout.addWidget(self.send_format_combo, 5, 1)
        
        # 接收格式
        layout.addWidget(QLabel("接收格式:"), 6, 0)
        self.display_format_combo = QComboBox()
        self.display_format_combo.addItems(["文本", "Hex"])
        self.display_format_combo.setCurrentText("Hex")  # 默认为Hex
        self.display_format_combo.currentTextChanged.connect(self.on_display_format_changed)
        layout.addWidget(self.display_format_combo, 6, 1)
        
        # 连接/断开按钮
        self.connect_btn = QPushButton("连接")
        self.connect_btn.clicked.connect(self.toggle_serial_connection)
        layout.addWidget(self.connect_btn, 7, 0, 1, 3)
        
        # 状态标签
        self.status_label = QLabel("状态: 未连接")
        layout.addWidget(self.status_label, 8, 0, 1, 3)
        
        return group
        
    def create_file_selection_group(self):
        """创建文件选择组"""
        group = QGroupBox("升级文件")
        layout = QVBoxLayout(group)
        
        # 文件路径显示
        self.file_path_edit = QLineEdit()
        self.file_path_edit.setPlaceholderText("请选择升级文件(hex/bin)")
        self.file_path_edit.setReadOnly(True)
        layout.addWidget(self.file_path_edit)
        
        # 文件选择按钮
        file_btn_layout = QHBoxLayout()
        
        select_file_btn = QPushButton("选择文件")
        select_file_btn.clicked.connect(self.select_upgrade_file)
        file_btn_layout.addWidget(select_file_btn)
        
        start_upgrade_btn = QPushButton("开始升级")
        start_upgrade_btn.clicked.connect(self.start_upgrade)
        start_upgrade_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
        file_btn_layout.addWidget(start_upgrade_btn)
        
        layout.addLayout(file_btn_layout)
        
        # 文件属性显示组
        file_info_group = QGroupBox("文件属性")
        file_info_layout = QVBoxLayout(file_info_group)
        
        # 文件属性显示区域
        self.file_info_display = QTextEdit()
        self.file_info_display.setReadOnly(True)
        self.file_info_display.setMaximumHeight(150)
        self.file_info_display.setFont(QFont("Consolas", 9))
        self.file_info_display.setPlaceholderText("选择文件后将显示文件属性信息...")
        file_info_layout.addWidget(self.file_info_display)
        
        layout.addWidget(file_info_group)
        
        # 传输状态组
        status_group = QGroupBox("传输状态")
        status_layout = QVBoxLayout(status_group)
        
        # 进度条
        self.transfer_progress_bar = QProgressBar()
        self.transfer_progress_bar.setVisible(False)
        status_layout.addWidget(self.transfer_progress_bar)
        
        # 状态标签
        self.transfer_status_label = QLabel("就绪")
        self.transfer_status_label.setFont(QFont("Arial", 10))
        status_layout.addWidget(self.transfer_status_label)
        
        layout.addWidget(status_group)
        
        return group
        
    def create_data_send_group(self):
        """创建数据发送组"""
        group = QGroupBox("数据发送")
        layout = QVBoxLayout(group)
        
        # 发送数据输入框
        self.send_data_edit = QTextEdit()
        self.send_data_edit.setMaximumHeight(80)
        self.send_data_edit.setPlaceholderText("输入要发送的数据...")
        layout.addWidget(self.send_data_edit)
        
        # 发送按钮
        send_btn = QPushButton("发送")
        send_btn.clicked.connect(self.send_data)
        layout.addWidget(send_btn)
        
        return group
        
    def create_file_selection_panel(self):
        """创建文件选择面板"""
        panel = QFrame()
        panel.setFrameStyle(QFrame.Box)
        layout = QVBoxLayout(panel)
        
        # 面板标题
        title = QLabel("文件选择")
        title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title)
        
        # 文件选择组
        file_group = self.create_file_selection_group()
        layout.addWidget(file_group)
        
        # 添加弹性空间
        layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        
        return panel
        
    def create_protocol_config_panel(self):
        """创建通讯协议选择与配置面板"""
        group = QGroupBox("通讯协议与配置")
        protocol_layout = QVBoxLayout(group)
        
        # 协议选择
        protocol_selection_group = self.create_protocol_selection_group()
        protocol_layout.addWidget(protocol_selection_group)
        
        # 动态配置区域
        self.dynamic_config_widget = DynamicProtocolWidget()
        protocol_layout.addWidget(self.dynamic_config_widget)
        
        # 添加占位符以保持布局
        protocol_layout.addStretch()
        self.init_protocol_config()
        return group
        
    def create_protocol_selection_group(self):
        """创建协议选择组"""
        group = QGroupBox("协议选择")
        layout = QVBoxLayout(group)
        
        # 协议选择
        self.protocol_combo = QComboBox()
        
        # 加载可用协议
        available_protocols = self.protocol_manager.get_available_protocols()
        if available_protocols:
            self.protocol_combo.addItems(available_protocols)
        else:
            self.protocol_combo.addItems(["暂无可用协议"])
            
        # 连接协议切换信号
        self.protocol_combo.currentTextChanged.connect(self.on_protocol_changed)
        layout.addWidget(self.protocol_combo)
        
        return group
        
    def init_protocol_config(self):
        """初始化协议配置"""
        # 获取可用协议并初始化第一个协议的配置
        available_protocols = self.protocol_manager.get_available_protocols()
        if available_protocols:
            # 触发协议切换以加载默认协议配置
            self.on_protocol_changed(available_protocols[0])
        
    def refresh_ports(self):
        """刷新可用串口列表"""
        self.port_combo.clear()
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_combo.addItem(f"{port.device} - {port.description}")
            
    def toggle_serial_connection(self):
        """切换串口连接状态"""
        if self.serial_port is None or not self.serial_port.is_open:
            self.connect_serial()
        else:
            self.disconnect_serial()
            
    def connect_serial(self):
        """连接串口"""
        try:
            port_text = self.port_combo.currentText()
            if not port_text:
                self.append_data_display("错误: 请选择串口端口", "red")
                return
                
            port = port_text.split(" - ")[0]
            
            # 处理波特率（支持自定义输入）
            baudrate_text = self.baudrate_combo.currentText().strip()
            if not baudrate_text:
                self.append_data_display("错误: 请输入波特率", "red")
                return
            try:
                baudrate = int(baudrate_text)
                if baudrate <= 0:
                    self.append_data_display("错误: 波特率必须大于0", "red")
                    return
            except ValueError:
                self.append_data_display("错误: 波特率必须为数字", "red")
                return
                
            databits = int(self.databits_combo.currentText())
            
            # 停止位映射
            stopbits_map = {"1": serial.STOPBITS_ONE, "1.5": serial.STOPBITS_ONE_POINT_FIVE, "2": serial.STOPBITS_TWO}
            stopbits = stopbits_map[self.stopbits_combo.currentText()]
            
            # 校验位映射
            parity_map = {"None": serial.PARITY_NONE, "Even": serial.PARITY_EVEN, 
                         "Odd": serial.PARITY_ODD, "Mark": serial.PARITY_MARK, "Space": serial.PARITY_SPACE}
            parity = parity_map[self.parity_combo.currentText()]
            
            # 创建串口对象
            self.serial_port = serial.Serial(
                port=port,
                baudrate=baudrate,
                bytesize=databits,
                stopbits=stopbits,
                parity=parity,
                timeout=1
            )
            
            # 启动接收线程
            self.serial_thread = SerialThread(self.serial_port)
            self.serial_thread.data_received.connect(self.handle_received_data)
            self.serial_thread.start()
            
            # 更新UI状态
            self.connect_btn.setText("断开")
            self.status_label.setText(f"状态: 已连接 ({port})")
            self.append_data_display(f"串口连接成功: {port}", "blue")
            
        except Exception as e:
            self.append_data_display(f"串口连接失败: {str(e)}", "red")
            
    def disconnect_serial(self):
        """断开串口连接"""
        if self.serial_thread:
            self.serial_thread.stop()
            self.serial_thread.wait()
            self.serial_thread = None
        
        if self.serial_port and self.serial_port.is_open:
            self.serial_port.close()
            
        self.connect_btn.setText("连接")
        self.status_label.setText("状态: 未连接")
        self.append_data_display("串口已断开", "blue")
        
        # 重新启用端口刷新按钮
        self.port_combo.parentWidget().findChild(QPushButton, "refresh_btn").setEnabled(True)

    def send_data(self):
        """发送数据"""
        if not self.serial_port or not self.serial_port.is_open:
            self.append_data_display("错误: 串口未连接", "red")
            return
            
        data_to_send_str = self.send_data_edit.toPlainText()
        if not data_to_send_str:
            return
            
        try:
            if self.send_format_combo.currentText() == "Hex":
                # Hex格式发送
                data_to_send = bytes.fromhex(data_to_send_str.replace(" ", "").replace("\n", ""))
            else:
                # 文本格式发送
                data_to_send = data_to_send_str.encode('utf-8')
                
            self.serial_port.write(data_to_send)
            
            timestamp = time.strftime("%H:%M:%S")
            
            if self.send_format_combo.currentText() == "Hex":
                display_text = f"[{timestamp}] 发送(Hex): {self.bytes_to_hex_string(data_to_send)}"
            else:
                display_text = f"[{timestamp}] 发送: {data_to_send_str}"
                
            self.append_data_display(display_text, "red")

            # 对发送的数据进行解析
            self.parse_data(data_to_send, "发送", timestamp)
            
        except ValueError:
            self.append_data_display("错误: Hex格式无效，请输入正确的十六进制字符 (0-9, A-F)", "red")
        except Exception as e:
            self.append_data_display(f"发送失败: {str(e)}", "red")
            
    def bytes_to_hex_string(self, data_bytes):
        """将字节数组转换为Hex字符串"""
        return " ".join([f"{b:02X}" for b in data_bytes])
        
    def on_send_format_changed(self, format_text):
        """发送格式改变时的处理"""
        if format_text == "Hex":
            self.send_data_edit.setPlaceholderText("输入十六进制数据 (例如: AA BB CC 或 AABBCC)")
        else:
            self.send_data_edit.setPlaceholderText("输入文本数据...")
            
    def on_display_format_changed(self, format_text):
        """显示格式改变时的处理"""
        pass  # 显示格式的处理在接收数据时进行
        
    def on_protocol_changed(self, protocol_name):
        """协议改变时的处理"""
        if protocol_name and protocol_name != "暂无可用协议":
            # 创建并显示协议配置界面
            config_widget = self.protocol_manager.create_config_widget(protocol_name)
            if hasattr(self, 'dynamic_config_widget'):
                self.dynamic_config_widget.set_protocol_widget(config_widget)
            
            # 显示协议描述
            description = self.protocol_manager.get_protocol_description(protocol_name)
            if hasattr(self, 'data_display'):
                self.append_data_display(f"已选择协议: {protocol_name} - {description}", "blue")
        else:
            if hasattr(self, 'dynamic_config_widget'):
                self.dynamic_config_widget.clear()
    
    def handle_protocol_status(self, message, color):
        """处理协议状态更新"""
        self.append_data_display(message, color)
        
        # 更新文件选择面板中的状态标签
        self.transfer_status_label.setText(message)
    
    def handle_protocol_progress(self, current, total):
        """处理协议进度更新"""
        # 更新文件选择面板中的进度条
        if total > 0:
            self.transfer_progress_bar.setVisible(True)
            self.transfer_progress_bar.setMaximum(total)
            self.transfer_progress_bar.setValue(current)
            percentage = int((current / total) * 100)
            self.transfer_progress_bar.setFormat(f"{current}/{total} ({percentage}%)")
        else:
            self.transfer_progress_bar.setVisible(False)
     
    def start_upgrade(self):
        """开始升级功能"""
        file_path = self.file_path_edit.text()
        if not file_path:
            self.append_data_display("错误: 请先选择升级文件", "red")
            return
            
        if not self.serial_port or not self.serial_port.is_open:
            self.append_data_display("错误: 串口未连接", "red")
            return
            
        # 获取当前选择的协议
        protocol_name = self.protocol_combo.currentText()
        if not protocol_name or protocol_name == "暂无可用协议":
            self.append_data_display("错误: 请选择有效的协议", "red")
            return
            
        # 获取协议配置
        current_widget = self.dynamic_config_widget.get_current_widget()
        if not current_widget:
            self.append_data_display("错误: 协议配置界面未加载", "red")
            return
            
        config = current_widget.get_config()
        
        # 验证配置
        is_valid, message = self.protocol_manager.validate_protocol_config(protocol_name, config)
        if not is_valid:
            self.append_data_display(f"配置错误: {message}", "red")
            return
            
        try:
            # 创建协议实例
            self.current_protocol = self.protocol_manager.create_protocol_instance(protocol_name, config)
            
            # 检查是否有预处理的文件数据
            if self.current_file_data:
                # 使用预处理的二进制数据
                file_name = os.path.basename(file_path)
                self.current_protocol.set_file_data(self.current_file_data, file_name)
                self.append_data_display(f"使用预处理的二进制数据进行传输，大小: {len(self.current_file_data)} 字节", "blue")
            else:
                # 直接使用文件路径
                self.current_protocol.set_file_path(file_path)
                self.append_data_display("直接读取文件进行传输", "blue")
            
            # 再次设置配置以确保最新的配置被应用
            self.current_protocol.set_config(config)
            
            # 连接协议信号
            self.current_protocol.data_send.connect(self.send_protocol_data)
            self.current_protocol.transfer_complete.connect(self.handle_transfer_complete)
            
            # 连接串口数据到协议
            if self.serial_thread:
                self.serial_thread.data_received.connect(self.current_protocol.handle_received_data)
            
            # 重置进度条
            self.transfer_progress_bar.setVisible(False)
            self.transfer_progress_bar.setValue(0)
            self.transfer_status_label.setText("准备开始传输...")
                
            self.append_data_display(f"开始使用{protocol_name}协议进行升级...", "blue")
            
            # 启动协议传输
            self.current_protocol.start()
            
        except Exception as e:
            self.append_data_display(f"升级启动失败: {str(e)}", "red")
    
    def send_protocol_data(self, data):
        """发送协议数据到串口"""
        if self.serial_port and self.serial_port.is_open:
            try:
                self.serial_port.write(data)
                
                # 显示发送的数据（红色）
                timestamp = time.strftime("%H:%M:%S")
                hex_text = self.bytes_to_hex_string(data)
                display_text = f"[{timestamp}] 协议发送(Hex): {hex_text}"
                self.append_data_display(display_text, "red")
                
            except Exception as e:
                self.append_data_display(f"协议数据发送失败: {str(e)}", "red")
    
    def handle_transfer_complete(self, success, message):
        """处理协议传输完成"""
        # 断开协议与串口的连接
        if self.current_protocol and self.serial_thread:
            # 安全地断开连接
            try:
                self.serial_thread.data_received.disconnect(self.current_protocol.handle_received_data)
            except RuntimeError:
                pass  # 如果连接不存在，则忽略

        if success:
            self.append_data_display(f"升级成功: {message}", "green")
            self.transfer_status_label.setText("升级成功")
        else:
            self.append_data_display(f"升级失败: {message}", "red")
            self.transfer_status_label.setText("升级失败")
            
    def handle_received_data(self, data):
        """处理接收到的数据"""
        try:
            timestamp = time.strftime("%H:%M:%S")
            
            # 根据显示格式处理数据
            if self.display_format_combo.currentText() == "Hex":
                # Hex格式显示
                hex_text = self.bytes_to_hex_string(data)
                display_text = f"[{timestamp}] 接收(Hex): {hex_text}"
            else:
                # 文本格式显示
                text = data.decode('utf-8', errors='ignore')
                display_text = f"[{timestamp}] 接收: {text}"
            
            # 显示接收的数据（绿色）
            self.append_data_display(display_text, "green")

            # 对接收的数据进行解析
            self.parse_data(data, "接收", timestamp)

        except Exception as e:
            self.append_data_display(f"数据处理错误: {str(e)}", "red")
            
    def parse_data(self, data_buffer, direction_str, timestamp):
        """根据方向解析数据并更新UI"""
        target_direction = f"解析{direction_str}数据"
        
        for i in range(self.parser_tabs.count()):
            parser_widget = self.parser_tabs.widget(i)
            if parser_widget and parser_widget.get_direction() == target_direction:
                parser = parser_widget.get_selected_parser()
                if not parser:
                    continue

                # 独立包解析：每个数据包独立解析
                new_data_hex = self.bytes_to_hex_string(data_buffer)
                print(f"DEBUG: 独立包解析 - 接收到新数据包 {len(data_buffer)} 字节: {new_data_hex}")
                
                # 检查数据包长度是否符合帧长度
                if len(data_buffer) == parser.frame_length:
                    result, error_msg = parser.parse(data_buffer)
                    
                    if result:
                        raw_frame_hex = self.bytes_to_hex_string(data_buffer)
                        parser_widget.update_parser_tree(result, timestamp, raw_frame_hex, direction_str)
                        print(f"DEBUG: 独立包解析成功 - {len(data_buffer)}字节完整帧")
                    else:
                        print(f"DEBUG: 独立包解析失败 - {error_msg}")
                else:
                    print(f"DEBUG: 独立包解析 - 数据包长度不匹配，期望{parser.frame_length}字节，实际{len(data_buffer)}字节")

    def append_data_display(self, text, color):
        """在数据显示区域添加文本"""
        cursor = self.data_display.textCursor()
        cursor.movePosition(QTextCursor.End)
        
        # 设置文本格式
        format = QTextCharFormat()
        if color == "red":
            format.setForeground(QColor(255, 100, 100))
        elif color == "green":
            format.setForeground(QColor(100, 255, 100))
        elif color == "blue":
            format.setForeground(QColor(100, 150, 255))
        else:
            format.setForeground(QColor(255, 255, 255))
            
        cursor.setCharFormat(format)
        cursor.insertText(text + "\n")
        
        # 自动滚动到底部 - 使用更可靠的方法
        self.data_display.moveCursor(QTextCursor.End)
        # 确保滚动条滚动到底部
        scrollbar = self.data_display.verticalScrollBar()
        scrollbar.setValue(scrollbar.maximum())
        
    def clear_data_display(self):
        """清空数据显示"""
        self.data_display.clear()
        
    def save_log(self):
        """保存通讯日志"""
        content = self.data_display.toPlainText()
        if not content:
            return
            
        file_path, _ = QFileDialog.getSaveFileName(
            self, "保存通讯日志", f"通讯日志_{time.strftime('%Y%m%d_%H%M%S')}.txt", 
            "Text Files (*.txt);;All Files (*)"
        )
        
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                self.append_data_display(f"日志已保存: {file_path}", "blue")
            except Exception as e:
                self.append_data_display(f"保存日志失败: {str(e)}", "red")
                
    def select_upgrade_file(self):
        """选择升级文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择升级文件", "", "Firmware Files (*.hex *.bin);;All Files (*)"
        )
        
        if file_path:
            self.file_path_edit.setText(file_path)
            self.append_data_display(f"已选择升级文件: {os.path.basename(file_path)}", "blue")
            
            # 解析并显示文件属性
            self.analyze_and_display_file_info(file_path)
    
    def analyze_and_display_file_info(self, file_path):
        """分析并显示文件信息"""
        try:
            self.current_file_data = None  # 重置文件数据
            
            if is_hex_file(file_path):
                # HEX文件处理
                self.append_data_display("检测到HEX文件，正在解析...", "blue")
                
                if self.hex_parser.parse_file(file_path):
                    # 解析成功，获取属性和二进制数据
                    properties = self.hex_parser.get_file_properties()
                    self.current_file_data = self.hex_parser.get_binary_data()
                    
                    # 显示文件属性
                    info_text = "=== HEX文件属性 ===\n"
                    for key, value in properties.items():
                        info_text += f"{key}: {value}\n"
                    
                    info_text += f"\n=== 传输信息 ===\n"
                    info_text += f"传输格式: 二进制数据 (BIN)\n"
                    info_text += f"传输大小: {len(self.current_file_data)} 字节\n"
                    
                    self.file_info_display.setText(info_text)
                    self.append_data_display(f"HEX文件解析成功，二进制数据大小: {len(self.current_file_data)} 字节", "blue")
                else:
                    self.file_info_display.setText("HEX文件解析失败！\n请检查文件格式是否正确。")
                    self.append_data_display("HEX文件解析失败", "red")
            
            else:
                # BIN文件或其他文件处理
                self.append_data_display("检测到BIN文件或其他格式文件", "blue")
                
                try:
                    with open(file_path, 'rb') as f:
                        self.current_file_data = f.read()
                    
                    # 显示BIN文件属性
                    file_size = len(self.current_file_data)
                    file_name = os.path.basename(file_path)
                    
                    info_text = "=== BIN文件属性 ===\n"
                    info_text += f"文件名: {file_name}\n"
                    info_text += f"文件路径: {file_path}\n"
                    info_text += f"文件大小: {file_size} 字节\n"
                    info_text += f"文件类型: 二进制文件\n"
                    
                    # 计算简单校验和
                    if self.current_file_data:
                        checksum = sum(self.current_file_data) & 0xFFFFFFFF
                        info_text += f"数据校验和: 0x{checksum:08X}\n"
                    
                    info_text += f"\n=== 传输信息 ===\n"
                    info_text += f"传输格式: 二进制数据 (BIN)\n"
                    info_text += f"传输大小: {file_size} 字节\n"
                    
                    self.file_info_display.setText(info_text)
                    self.append_data_display(f"BIN文件读取成功，文件大小: {file_size} 字节", "blue")
                    
                except Exception as e:
                    self.file_info_display.setText(f"文件读取失败：{str(e)}")
                    self.append_data_display(f"文件读取失败: {str(e)}", "red")
                    
        except Exception as e:
            self.file_info_display.setText(f"文件分析失败：{str(e)}")
            self.append_data_display(f"文件分析失败: {str(e)}", "red")
            
    def apply_dark_theme(self):
        """应用暗黑科技风主题"""
        dark_style = """
        QMainWindow {
            background-color: #1e1e1e;
            color: #ffffff;
        }
        
        QWidget {
            background-color: #1e1e1e;
            color: #ffffff;
            font-family: 'Microsoft YaHei', 'Arial';
        }
        
        QGroupBox {
            font-weight: bold;
            border: 2px solid #3daee9;
            border-radius: 8px;
            margin-top: 10px;
            padding-top: 10px;
            background-color: #2d2d2d;
        }
        
        QGroupBox::title {
            subcontrol-origin: margin;
            left: 10px;
            padding: 0 8px 0 8px;
            color: #3daee9;
        }
        
        QLabel {
            background-color: transparent;
            color: #ffffff;
        }
        
        QComboBox {
            background-color: #3c3c3c;
            border: 1px solid #5a5a5a;
            border-radius: 4px;
            padding: 4px;
            color: #ffffff;
        }
        
        QComboBox::drop-down {
            border: none;
        }
        
        QComboBox::down-arrow {
            image: none;
            border-style: solid;
            border-width: 5px 3px 0 3px;
            border-color: #3daee9 transparent transparent transparent;
        }
        
        QPushButton {
            background-color: #0e4b99;
            border: 1px solid #3daee9;
            border-radius: 6px;
            color: #ffffff;
            padding: 8px 16px;
            font-weight: bold;
        }
        
        QPushButton:hover {
            background-color: #3daee9;
        }
        
        QPushButton:pressed {
            background-color: #2980b9;
        }
        
        QLineEdit {
            background-color: #3c3c3c;
            border: 1px solid #5a5a5a;
            border-radius: 4px;
            padding: 6px;
            color: #ffffff;
        }
        
        QLineEdit:focus {
            border-color: #3daee9;
        }
        
        QTextEdit {
            background-color: #2d2d2d;
            border: 1px solid #5a5a5a;
            border-radius: 4px;
            color: #ffffff;
            selection-background-color: #3daee9;
        }
        
        QFrame {
            background-color: #2d2d2d;
            border: 1px solid #3daee9;
            border-radius: 8px;
        }
        
        QScrollBar:vertical {
            background-color: #3c3c3c;
            width: 12px;
            border-radius: 6px;
        }
        
        QScrollBar::handle:vertical {
            background-color: #5a5a5a;
            border-radius: 6px;
            min-height: 20px;
        }
        
        QScrollBar::handle:vertical:hover {
            background-color: #3daee9;
        }
        
        QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
            height: 0px;
        }
        
        QProgressBar {
            background-color: #3c3c3c;
            border: 1px solid #5a5a5a;
            border-radius: 4px;
            text-align: center;
            color: #ffffff;
        }
        
        QProgressBar::chunk {
            background-color: #3daee9;
            border-radius: 3px;
        }

        QTabWidget::pane {
            border: 1px solid #3daee9;
            border-radius: 4px;
            background-color: #2d2d2d;
        }

        QTabBar::tab {
            background-color: #2d2d2d;
            color: #ffffff;
            border: 1px solid #3daee9;
            border-bottom: none;
            border-top-left-radius: 6px;
            border-top-right-radius: 6px;
            padding: 8px 16px;
            margin-right: 2px;
        }

        QTabBar::tab:selected {
            background-color: #3daee9;
            color: #ffffff;
            font-weight: bold;
        }

        QTabBar::tab:hover {
            background-color: #4a5d6a;
        }
        """
        self.setStyleSheet(dark_style)
        
    def closeEvent(self, event):
        """窗口关闭事件"""
        if self.serial_thread:
            self.serial_thread.stop()
            self.serial_thread.wait()
        event.accept()

    def create_frame_parser_container(self):
        """创建数据帧解析功能的容器"""
        container = QWidget()
        layout = QVBoxLayout(container)
        layout.setContentsMargins(0, 5, 0, 0)

        # 解析器实例的Tab
        self.parser_tabs = QTabWidget()
        self.parser_tabs.setTabsClosable(True)
        self.parser_tabs.tabCloseRequested.connect(self.close_parser_tab)

        # 创建并设置 '添加' 按钮
        add_icon = qta.icon('fa5s.plus', color='#5a5a5a', color_active='#ffffff')
        add_parser_btn = QPushButton(add_icon, "")
        add_parser_btn.setFixedSize(28, 28)
        # 为按钮应用特定样式以优化外观
        add_parser_btn.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: 1px dashed #5a5a5a;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #4a5d6a;
                border-style: solid;
            }
            QPushButton:pressed {
                background-color: #2980b9;
            }
        """)
        add_parser_btn.setToolTip("添加一个新的解析器")
        add_parser_btn.clicked.connect(self.add_parser_tab)

        # 将按钮放置在Tab栏的左上角
        self.parser_tabs.setCornerWidget(add_parser_btn, Qt.TopLeftCorner)

        layout.addWidget(self.parser_tabs)
        
        # 默认添加一个
        self.add_parser_tab()

        return container

    def add_parser_tab(self):
        """添加一个新的解析器实例标签页"""
        parser_widget = ParserInstanceWidget(self.frame_parsers)
        
        # 初始标签名
        tab_index = self.parser_tabs.addTab(parser_widget, f"解析器 {self.parser_tabs.count() + 1}")
        self.parser_tabs.setCurrentIndex(tab_index)
        
        # 手动触发一次，以设置正确的初始名称
        parser_widget.on_parser_selected(parser_widget.protocol_combo.currentText())

    def close_parser_tab(self, index):
        """关闭一个解析器标签页"""
        widget = self.parser_tabs.widget(index)
        if widget:
            widget.deleteLater()
        self.parser_tabs.removeTab(index)

    def create_data_display_panel(self):
        """创建通讯数据显示面板"""
        panel = QFrame()
        panel.setFrameStyle(QFrame.Box)
        layout = QVBoxLayout(panel)
        
        # 面板标题
        title = QLabel("通讯数据显示")
        title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title)
        
        # 数据显示区域
        self.data_display = QTextEdit()
        self.data_display.setReadOnly(True)
        self.data_display.setFont(QFont("Consolas", 9))
        # 设置最大行数限制，防止界面抖动
        self.data_display.document().setMaximumBlockCount(1000)
        layout.addWidget(self.data_display)
        
        # 操作按钮
        btn_layout = QHBoxLayout()
        
        clear_btn = QPushButton("清空")
        clear_btn.clicked.connect(self.clear_data_display)
        btn_layout.addWidget(clear_btn)
        
        save_btn = QPushButton("保存日志")
        save_btn.clicked.connect(self.save_log)
        btn_layout.addWidget(save_btn)
        
        layout.addLayout(btn_layout)
        
        return panel
        
    def create_manual_send_panel(self):
        """创建手动发送数据面板"""
        panel = QFrame()
        panel.setFrameStyle(QFrame.Box)
        layout = QVBoxLayout(panel)
        
        # 面板标题
        title = QLabel("手动发送数据")
        title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title)
        
        # 数据发送组
        send_group = self.create_data_send_group()
        layout.addWidget(send_group)
        
        # 添加弹性空间
        layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        
        return panel

    def create_device_control_widget(self):
        """创建设备控制面板"""
        panel = QFrame()
        panel.setFrameStyle(QFrame.Box)
        layout = QVBoxLayout(panel)
        
        # 面板标题
        title = QLabel("设备控制")
        title.setFont(QFont("Arial", 12, QFont.Bold))
        layout.addWidget(title)
        
        # 工具栏
        toolbar_layout = QHBoxLayout()
        
        # 添加普通按钮
        add_btn = QPushButton("添加普通按钮")
        add_btn.clicked.connect(self.add_control_button)
        toolbar_layout.addWidget(add_btn)
        
        # 添加自定义按钮
        add_custom_btn = QPushButton("添加自定义按钮")
        add_custom_btn.clicked.connect(self.add_custom_control_button)
        toolbar_layout.addWidget(add_custom_btn)
        
        # 保存配置
        save_btn = QPushButton("保存配置")
        save_btn.clicked.connect(self.save_device_config)
        toolbar_layout.addWidget(save_btn)
        
        # 加载配置
        load_btn = QPushButton("加载配置")
        load_btn.clicked.connect(self.load_device_config)
        toolbar_layout.addWidget(load_btn)
        
        # 清空所有按钮
        clear_btn = QPushButton("清空所有")
        clear_btn.clicked.connect(self.clear_all_buttons)
        toolbar_layout.addWidget(clear_btn)
        
        toolbar_layout.addStretch()
        layout.addLayout(toolbar_layout)
        
        # 创建可拖拽的按钮容器
        self.button_container = QWidget()
        self.button_container.setMinimumSize(400, 300)
        self.button_container.setStyleSheet("QWidget { background-color: #2b2b2b; border: 1px solid #555; }")
        self.button_container.mousePressEvent = self.on_container_click
        
        # 按钮列表
        self.control_buttons = []
        
        layout.addWidget(self.button_container)
        
        return panel
        
    def add_control_button(self):
        """添加控制按钮"""
        button_info = self.show_button_dialog()
        if button_info:
            self.create_control_button(button_info)
            
    def add_custom_control_button(self):
        """添加自定义按钮（分段定义）"""
        try:
            button_info = self.show_custom_button_dialog()
            if not button_info:
                return
            # 如定义包含可变段，则自动带输入框
            has_variable = any(seg.get('type') == 'variable' for seg in button_info.get('segments', []))
            if has_variable and not button_info.get('has_input'):
                button_info['has_input'] = True
                button_info['input_label'] = button_info.get('input_label', '插入数据')
                button_info['dynamic_format'] = button_info.get('dynamic_format', 'Hex')
            button_info['custom_mode'] = True
            self.create_control_button(button_info)
        except Exception as e:
            self.append_data_display(f"添加自定义按钮失败: {e}", "red")

    def show_button_dialog(self, defaults=None):
        """显示按钮配置对话框"""
        dialog = QDialog(self)
        dialog.setWindowTitle("按钮配置")
        dialog.setModal(True)
        dialog.resize(360, 420)
        
        layout = QFormLayout(dialog)
        
        # 按钮名称
        name_edit = QLineEdit()
        name_edit.setPlaceholderText("按钮名称")
        if defaults and 'name' in defaults:
            name_edit.setText(str(defaults['name']))
        layout.addRow("按钮名称:", name_edit)
        
        # X坐标
        x_spin = QSpinBox()
        x_spin.setRange(0, 2000)
        x_spin.setValue(int(defaults.get('x', 50)) if defaults else 50)
        layout.addRow("X坐标:", x_spin)
        
        # Y坐标
        y_spin = QSpinBox()
        y_spin.setRange(0, 2000)
        y_spin.setValue(int(defaults.get('y', 50)) if defaults else 50)
        layout.addRow("Y坐标:", y_spin)
        
        # 宽度
        width_spin = QSpinBox()
        width_spin.setRange(40, 1000)
        width_spin.setValue(int(defaults.get('width', 100)) if defaults else 100)
        layout.addRow("宽度:", width_spin)
        
        # 高度
        height_spin = QSpinBox()
        height_spin.setRange(22, 500)
        height_spin.setValue(int(defaults.get('height', 40)) if defaults else 40)
        layout.addRow("高度:", height_spin)
        
        # 固定数据
        data_edit = QLineEdit()
        data_edit.setPlaceholderText("固定数据: AA BB CC 或 文本")
        if defaults and 'data' in defaults:
            data_edit.setText(str(defaults['data']))
        layout.addRow("固定数据:", data_edit)
        
        # 固定数据格式
        format_combo = QComboBox()
        format_combo.addItems(["Hex", "文本"])
        if defaults and defaults.get('format') in ["Hex", "文本"]:
            format_combo.setCurrentText(defaults.get('format'))
        else:
            format_combo.setCurrentText("Hex")
        layout.addRow("固定数据格式:", format_combo)
        
        # 是否有输入框
        has_input_check = QCheckBox("为该按钮添加输入框")
        has_input_check.setChecked(bool(defaults.get('has_input')) if defaults else False)
        layout.addRow(has_input_check)
        
        # 输入框标签
        input_label_edit = QLineEdit()
        input_label_edit.setPlaceholderText("输入框提示，例如: 输入值")
        if defaults and 'input_label' in defaults:
            input_label_edit.setText(str(defaults['input_label']))
        layout.addRow("输入提示:", input_label_edit)
        
        # 动态数据格式
        dynamic_format_combo = QComboBox()
        dynamic_format_combo.addItems(["Hex", "文本"])
        if defaults and defaults.get('dynamic_format') in ["Hex", "文本"]:
            dynamic_format_combo.setCurrentText(defaults.get('dynamic_format'))
        layout.addRow("动态数据格式:", dynamic_format_combo)
        
        # 模板：前缀/后缀/长度/校验
        prefix_edit = QLineEdit()
        prefix_edit.setPlaceholderText("前缀Hex，如 AA55")
        if defaults and defaults.get('template', {}).get('prefix'):
            prefix_edit.setText(defaults['template']['prefix'])
        layout.addRow("前缀Hex:", prefix_edit)
        
        suffix_edit = QLineEdit()
        suffix_edit.setPlaceholderText("后缀Hex，如 0D0A")
        if defaults and defaults.get('template', {}).get('suffix'):
            suffix_edit.setText(defaults['template']['suffix'])
        layout.addRow("后缀Hex:", suffix_edit)
        
        length_size_combo = QComboBox()
        length_size_combo.addItems(["0", "1", "2"])  # 长度字节数
        if defaults and str(defaults.get('template', {}).get('length_size', '0')) in ["0","1","2"]:
            length_size_combo.setCurrentText(str(defaults['template']['length_size']))
        layout.addRow("长度字节数:", length_size_combo)
        
        length_includes_combo = QComboBox()
        length_includes_combo.addItems(["insert", "all_without_checksum"])  # 计算范围
        if defaults and defaults.get('template', {}).get('length_includes') in ["insert", "all_without_checksum"]:
            length_includes_combo.setCurrentText(defaults['template']['length_includes'])
        layout.addRow("长度计算范围:", length_includes_combo)
        
        checksum_type_combo = QComboBox()
        checksum_type_combo.addItems(["none", "sum8", "xor8"])  # 校验类型
        if defaults and defaults.get('template', {}).get('checksum_type') in ["none","sum8","xor8"]:
            checksum_type_combo.setCurrentText(defaults['template']['checksum_type'])
        layout.addRow("校验类型:", checksum_type_combo)
        
        # 按钮
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        layout.addRow(button_box)
        
        if dialog.exec() == QDialog.Accepted:
            return {
                'name': name_edit.text(),
                'x': x_spin.value(),
                'y': y_spin.value(),
                'width': width_spin.value(),
                'height': height_spin.value(),
                'data': data_edit.text(),
                'format': format_combo.currentText(),
                'has_input': has_input_check.isChecked(),
                'input_label': input_label_edit.text(),
                'dynamic_format': dynamic_format_combo.currentText(),
                'template': {
                    'prefix': prefix_edit.text().strip(),
                    'suffix': suffix_edit.text().strip(),
                    'length_size': int(length_size_combo.currentText()),
                    'length_includes': length_includes_combo.currentText(),
                    'checksum_type': checksum_type_combo.currentText(),
                }
            }
        return None
        
    def create_control_button(self, button_info):
        """创建控制按钮"""
        button = QPushButton(button_info['name'])
        button.setGeometry(button_info['x'], button_info['y'], button_info['width'], button_info['height'])
        button.setParent(self.button_container)
        
        # 设置按钮样式
        button.setStyleSheet("""
            QPushButton {
                background-color: #4a4a4a;
                border: 2px solid #666;
                border-radius: 5px;
                color: white;
                font-weight: bold;
                padding: 5px;
            }
            QPushButton:hover {
                background-color: #5a5a5a;
                border-color: #888;
            }
            QPushButton:pressed {
                background-color: #3a3a3a;
                border-color: #555;
            }
        """)
        
        # 存储按钮信息
        button.button_info = button_info
        
        # 如需输入框，创建并放置
        if button_info.get('has_input'):
            input_edit = QLineEdit(self.button_container)
            input_edit.setPlaceholderText(button_info.get('input_label', '输入数据'))
            # 将输入框放置在按钮下方，宽度与按钮一致
            input_edit.setGeometry(
                button_info['x'],
                button_info['y'] + button_info['height'] + 4,
                button_info['width'],
                28
            )
            input_edit.show()
            button.button_input = input_edit
        else:
            button.button_input = None
        
        # 连接信号
        button.mousePressEvent = lambda event: self.on_button_click(event, button)
        
        button.show()
        self.control_buttons.append(button)
        
    def on_button_click(self, event, button):
        """按钮点击事件"""
        if event.button() == Qt.LeftButton:  # 左键点击 - 发送数据
            self.send_button_data(button)
        elif event.button() == Qt.RightButton:  # 右键点击 - 编辑按钮
            self.edit_button(button)
            
    def send_button_data(self, button):
        """发送按钮数据"""
        if not self.serial_port or not self.serial_port.is_open:
            self.append_data_display("错误: 串口未连接", "red")
            return
            
        button_info = button.button_info
        
        # 读取动态输入（供两种模式使用）
        dyn_bytes = b''
        try:
            if getattr(button, 'button_input', None) is not None:
                dyn_text = button.button_input.text().strip()
                if dyn_text:
                    if button_info.get('dynamic_format', 'Hex') == 'Hex':
                        dyn_bytes = bytes.fromhex(dyn_text.replace(' ', '').replace('\n', ''))
                    else:
                        dyn_bytes = dyn_text.encode('utf-8')
        except ValueError:
            self.append_data_display("错误: 动态输入Hex格式无效", "red")
            return
        
        # 自定义模式：按段填充
        if button_info.get('custom_mode'):
            try:
                data_bytes = self.build_custom_frame(button_info, dyn_bytes)
            except Exception as e:
                self.append_data_display(f"自定义组帧失败: {e}", "red")
                return
        else:
            # 读取模板字段（普通模式）
            tpl = button_info.get('template', {})
            prefix_hex = tpl.get('prefix', '').replace(' ', '')
            suffix_hex = tpl.get('suffix', '').replace(' ', '')
            length_size = int(tpl.get('length_size', 0))  # 0/1/2 字节，放在prefix之后
            length_includes = tpl.get('length_includes', 'insert')  # insert/all_without_checksum
            checksum_type = tpl.get('checksum_type', 'none')  # none/sum8/xor8
            
            try:
                prefix = bytes.fromhex(prefix_hex) if prefix_hex else b''
                suffix = bytes.fromhex(suffix_hex) if suffix_hex else b''
            except ValueError:
                self.append_data_display(f"错误: 按钮 '{button_info['name']}' 模板前后缀Hex无效", "red")
                return
            
            # 基础固定数据
            fixed_bytes = b''
            if button_info.get('data', ''): # 使用按钮的固定数据
                try:
                    if button_info.get('format', 'Hex') == 'Hex':
                        fixed_bytes = bytes.fromhex(button_info['data'].replace(' ', '').replace('\n', ''))
                    else:
                        fixed_bytes = button_info['data'].encode('utf-8')
                except ValueError:
                    self.append_data_display(f"错误: 按钮 '{button_info['name']}' 的Hex格式无效", "red")
                    return
            
            # 组帧：prefix + [len] + fixed + insert + [checksum] + suffix
            frame_parts = [prefix]
            if length_size in (1, 2):
                frame_parts.append(b'\x00' * length_size)
            frame_parts.append(fixed_bytes)
            frame_parts.append(b'') # 动态数据为空，因为输入框是空的
            pre_ck_bytes = b''.join(frame_parts)
            if length_size in (1, 2):
                if length_includes == 'insert':
                    length_value = 0 # 动态数据为空
                else: # all_without_checksum
                    length_value = len(pre_ck_bytes)
                if length_size == 1:
                    length_bytes = bytes([length_value & 0xFF])
                else:
                    length_bytes = length_value.to_bytes(2, 'big')
                frame_parts[1] = length_bytes
                pre_ck_bytes = b''.join(frame_parts)
            if checksum_type != 'none':
                if checksum_type == 'sum8':
                    ck = sum(pre_ck_bytes) & 0xFF
                elif checksum_type == 'xor8':
                    ck = 0
                    for b in pre_ck_bytes:
                        ck ^= b
                else:
                    ck = 0
                frame_parts.append(bytes([ck]))
            frame_parts.append(suffix)
            data_bytes = b''.join(frame_parts)
        
        # 统一发送与显示
        try:
            self.serial_port.write(data_bytes)
            timestamp = time.strftime("%H:%M:%S")
            hex_text = self.bytes_to_hex_string(data_bytes)
            display_text = f"[{timestamp}] 按钮'{button_info['name']}'发送: {hex_text}"
            self.append_data_display(display_text, "red")
            self.parse_data(data_bytes, f"按钮{button_info['name']}", timestamp)
        except Exception as e:
            self.append_data_display(f"按钮 '{button_info['name']}' 发送失败: {str(e)}", "red")

    def show_custom_button_dialog(self, defaults=None):
        """显示自定义按钮配置对话框（支持预填）"""
        dialog = QDialog(self)
        dialog.setWindowTitle("自定义按钮配置")
        dialog.setModal(True)
        dialog.resize(520, 660)
        
        root_layout = QVBoxLayout(dialog)
        form = QFormLayout()
        root_layout.addLayout(form)
        
        # 基础属性
        name_edit = QLineEdit(); name_edit.setPlaceholderText("按钮名称")
        if defaults: name_edit.setText(str(defaults.get('name','')))
        form.addRow("按钮名称:", name_edit)
        x_spin = QSpinBox(); x_spin.setRange(0, 2000); x_spin.setValue(int(defaults.get('x',50)) if defaults else 50)
        y_spin = QSpinBox(); y_spin.setRange(0, 2000); y_spin.setValue(int(defaults.get('y',50)) if defaults else 50)
        width_spin = QSpinBox(); width_spin.setRange(40, 1000); width_spin.setValue(int(defaults.get('width',120)) if defaults else 120)
        height_spin = QSpinBox(); height_spin.setRange(22, 500); height_spin.setValue(int(defaults.get('height',40)) if defaults else 40)
        form.addRow("X坐标:", x_spin)
        form.addRow("Y坐标:", y_spin)
        form.addRow("宽度:", width_spin)
        form.addRow("高度:", height_spin)
        has_input_check = QCheckBox("带输入框"); has_input_check.setChecked(bool(defaults.get('has_input')) if defaults else False); form.addRow(has_input_check)
        input_label_edit = QLineEdit(); input_label_edit.setPlaceholderText("输入提示，如: 插入数据");
        if defaults: input_label_edit.setText(str(defaults.get('input_label','')))
        form.addRow("输入提示:", input_label_edit)
        dynamic_format_combo = QComboBox(); dynamic_format_combo.addItems(["Hex","文本"])
        if defaults and defaults.get('dynamic_format') in ["Hex","文本"]:
            dynamic_format_combo.setCurrentText(defaults.get('dynamic_format'))
        form.addRow("动态数据格式:", dynamic_format_combo)
        total_length_spin = QSpinBox(); total_length_spin.setRange(1, 10000); total_length_spin.setValue(int(defaults.get('total_length',10)) if defaults else 10)
        form.addRow("总长度 (字节):", total_length_spin)
        
        # 段列表与操作
        self.segment_list = QListWidget()
        self.segment_list.setSelectionMode(QListWidget.SingleSelection)
        self.segment_list.setDragDropMode(QListWidget.InternalMove)
        self.segment_list.setDefaultDropAction(Qt.MoveAction)
        self.segment_list.setSpacing(4)
        self.segment_list.itemDoubleClicked.connect(self.edit_segment_item)
        root_layout.addWidget(self.segment_list)
        
        # 预填 segments
        if defaults and isinstance(defaults.get('segments'), list):
            from PySide6.QtWidgets import QListWidgetItem
            for seg in defaults['segments']:
                item = QListWidgetItem(self.segment_to_text(seg))
                item.setData(Qt.UserRole, seg)
                self.segment_list.addItem(item)
        
        ops_layout = QHBoxLayout()
        add_segment_btn = QPushButton("添加段"); add_segment_btn.clicked.connect(self.add_segment_to_list)
        up_btn = QPushButton("上移"); up_btn.clicked.connect(self.move_segment_up)
        down_btn = QPushButton("下移"); down_btn.clicked.connect(self.move_segment_down)
        del_segment_btn = QPushButton("删除选中的段"); del_segment_btn.clicked.connect(self.delete_selected_segments)
        ops_layout.addWidget(add_segment_btn); ops_layout.addWidget(up_btn); ops_layout.addWidget(down_btn); ops_layout.addWidget(del_segment_btn)
        root_layout.addLayout(ops_layout)
        
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        root_layout.addWidget(button_box)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        
        if dialog.exec() == QDialog.Accepted:
            return {
                'name': name_edit.text() or '自定义按钮',
                'x': x_spin.value(),
                'y': y_spin.value(),
                'width': width_spin.value(),
                'height': height_spin.value(),
                'custom_mode': True,
                'has_input': has_input_check.isChecked(),
                'input_label': input_label_edit.text(),
                'dynamic_format': dynamic_format_combo.currentText(),
                'total_length': total_length_spin.value(),
                'segments': self.get_segment_list_data()
            }
        return None

    def segment_to_text(self, seg: dict) -> str:
        t = seg.get('type')
        s = seg.get('start'); l = seg.get('length')
        note = seg.get('note')
        note_str = f", note={note}" if note else ""
        if t == 'fixed':
            v = seg.get('value','')
            return f"固定 | start={s}, len={l}, val={v}{note_str}"
        if t == 'variable':
            return f"变量 | start={s}, len={l}{note_str}"
        if t == 'length':
            v = seg.get('value',0)
            return f"长度 | start={s}, len={l}, val={v}{note_str}"
        if t == 'checksum':
            m = seg.get('method'); r0 = seg.get('range_start'); r1 = seg.get('range_end')
            return f"校验 | start={s}, len={l}, {m}, range=[{r0},{r1}]{note_str}"
        return str(seg)

    def add_segment_to_list(self):
        """添加一个新的段到列表中"""
        seg = self.segment_editor_dialog()
        if seg:
            from PySide6.QtWidgets import QListWidgetItem
            item = QListWidgetItem(self.segment_to_text(seg))
            item.setData(Qt.UserRole, seg)
            self.segment_list.addItem(item)
            self.segment_list.setCurrentItem(item)

    def edit_segment_item(self, item):
        seg = item.data(Qt.UserRole) or {}
        edited = self.segment_editor_dialog(seg)
        if edited:
            item.setData(Qt.UserRole, edited)
            item.setText(self.segment_to_text(edited))

    def segment_editor_dialog(self, defaults=None):
        dialog = QDialog(self)
        dialog.setWindowTitle("段配置")
        dialog.setModal(True)
        dialog.resize(380, 320)
        form = QFormLayout(dialog)
        
        type_combo = QComboBox(); type_combo.addItems(["固定","变量","长度","校验"])
        if defaults:
            map_type = {'fixed':'固定','variable':'变量','length':'长度','checksum':'校验'}
            type_combo.setCurrentText(map_type.get(defaults.get('type'),'固定'))
        form.addRow("段类型:", type_combo)
        
        start_spin = QSpinBox(); start_spin.setRange(0, 10000); start_spin.setValue(int(defaults.get('start',0)) if defaults else 0)
        length_spin = QSpinBox(); length_spin.setRange(1, 10000); length_spin.setValue(int(defaults.get('length',1)) if defaults else 1)
        form.addRow("起始下标:", start_spin)
        form.addRow("长度(字节):", length_spin)
        
        fixed_value = QLineEdit(); fixed_value.setPlaceholderText("Hex值，如 AA55")
        if defaults and defaults.get('type')=='fixed':
            fixed_value.setText(defaults.get('value',''))
        len_value = QSpinBox(); len_value.setRange(0, 65535); len_value.setValue(int(defaults.get('value',0)) if defaults and defaults.get('type')=='length' else 0)
        method_combo = QComboBox(); method_combo.addItems(["sum8","xor8","crc16"])
        if defaults and defaults.get('type')=='checksum':
            method_combo.setCurrentText(defaults.get('method','sum8'))
        range_start = QSpinBox(); range_start.setRange(0, 10000); range_start.setValue(int(defaults.get('range_start',0)) if defaults else 0)
        range_end = QSpinBox(); range_end.setRange(0, 10000); range_end.setValue(int(defaults.get('range_end',0)) if defaults else 0)
        
        # 备注
        note_edit = QLineEdit(); note_edit.setPlaceholderText("该段的备注信息")
        if defaults and 'note' in defaults:
            note_edit.setText(str(defaults.get('note','')))
        form.addRow("备注:", note_edit)
        
        def update_fields():
            t = type_combo.currentText()
            for w in (fixed_value, len_value, method_combo, range_start, range_end):
                w.setVisible(False)
            if t == "固定":
                fixed_value.setVisible(True); form.addRow("固定值Hex:", fixed_value)
            elif t == "长度":
                len_value.setVisible(True); form.addRow("固定长度值:", len_value)
            elif t == "校验":
                method_combo.setVisible(True); range_start.setVisible(True); range_end.setVisible(True)
                form.addRow("校验方法:", method_combo)
                form.addRow("校验范围起始:", range_start)
                form.addRow("校验范围结束:", range_end)
        update_fields()
        type_combo.currentTextChanged.connect(lambda _: update_fields())
        
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        form.addRow(button_box)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        
        if dialog.exec() == QDialog.Accepted:
            t = type_combo.currentText()
            seg = { 'start': start_spin.value(), 'length': length_spin.value(), 'note': note_edit.text().strip() }
            if t == "固定": seg.update({'type':'fixed','value': fixed_value.text().strip()})
            elif t == "变量": seg.update({'type':'variable'})
            elif t == "长度": seg.update({'type':'length','value': len_value.value()})
            else: seg.update({'type':'checksum','method': method_combo.currentText(),'range_start': range_start.value(),'range_end': range_end.value()})
            return seg
        return None

    def move_segment_up(self):
        item = self.segment_list.currentItem()
        if not item: return
        row = self.segment_list.row(item)
        if row <= 0: return
        self.segment_list.takeItem(row)
        self.segment_list.insertItem(row-1, item)
        self.segment_list.setCurrentItem(item)

    def move_segment_down(self):
        item = self.segment_list.currentItem()
        if not item: return
        row = self.segment_list.row(item)
        if row >= self.segment_list.count()-1: return
        self.segment_list.takeItem(row)
        self.segment_list.insertItem(row+1, item)
        self.segment_list.setCurrentItem(item)

    def delete_selected_segments(self):
        """删除选中的段"""
        selected_items = self.segment_list.selectedItems()
        for item in selected_items:
            row = self.segment_list.row(item)
            self.segment_list.takeItem(row)

    def get_segment_list_data(self):
        """从列表中提取段数据"""
        segments = []
        for i in range(self.segment_list.count()):
            item = self.segment_list.item(i)
            data = item.data(Qt.UserRole)
            if isinstance(data, dict):
                segments.append(data)
        return segments

    def build_custom_frame(self, button_info, dyn_bytes):
        """根据分段定义构建完整帧。
        规则：用户先定义 total_length，随后 segments 列表包含多种段：
        - fixed: {type:'fixed', start:0, length:2, value:'AA55'}
        - variable: {type:'variable', start:2, length:3}  # 来自输入框，必须满足长度
        - length: {type:'length', start:5, length:1, value:10}  # 固定长度值（十进制）
        - checksum: {type:'checksum', start:6, length:1, range_start:0, range_end:6, method:'sum8'|'xor8'|'crc16'}
        """
        total = int(button_info.get('total_length', 0))
        if total <= 0:
            raise ValueError('未设置总长度或总长度无效')
        frame = bytearray([0x00] * total)
        
        def put_bytes(start, data_bytes, expect_len=None):
            if start < 0 or start + len(data_bytes) > total:
                raise ValueError(f'段越界: start={start}, len={len(data_bytes)}')
            if expect_len is not None and len(data_bytes) != expect_len:
                raise ValueError(f'长度不匹配: 期望{expect_len}, 实际{len(data_bytes)}')
            frame[start:start+len(data_bytes)] = data_bytes
        
        # 先放固定、长度、变量，最后做校验
        for seg in button_info.get('segments', []):
            stype = seg.get('type')
            if stype == 'fixed':
                start = int(seg['start'])
                length = int(seg['length'])
                value_hex = str(seg.get('value', '')).replace(' ', '')
                data = bytes.fromhex(value_hex) if value_hex else b''
                put_bytes(start, data, expect_len=length)
            elif stype == 'length':
                start = int(seg['start'])
                length = int(seg['length'])
                value = int(seg.get('value', 0))
                data = value.to_bytes(length, 'big')
                put_bytes(start, data, expect_len=length)
            elif stype == 'variable':
                start = int(seg['start'])
                length = int(seg['length'])
                if len(dyn_bytes) != length:
                    raise ValueError(f'可变数据长度不符，需{length}字节，实际{len(dyn_bytes)}')
                put_bytes(start, dyn_bytes, expect_len=length)
            elif stype == 'checksum':
                # checksum 延后处理
                continue
            else:
                raise ValueError(f'未知段类型: {stype}')
        
        # 校验段处理
        for seg in button_info.get('segments', []):
            if seg.get('type') != 'checksum':
                continue
            start = int(seg['start'])
            length = int(seg['length'])
            r0 = int(seg.get('range_start', 0))
            r1 = int(seg.get('range_end', total))
            method = seg.get('method', 'sum8')
            # 采用闭区间 [r0, r1]（包含结束下标）
            if r0 < 0 or r1 < 0 or r0 >= total or r1 >= total or r0 > r1:
                raise ValueError('校验范围无效')
            region = bytes(frame[r0:r1 + 1])
            if method == 'sum8':
                ck = sum(region) & 0xFF
                data = bytes([ck])
            elif method == 'xor8':
                ck = 0
                for b in region:
                    ck ^= b
                data = bytes([ck])
            elif method == 'crc16':
                crc = self.crc16(region)
                data = crc.to_bytes(2, 'big')
            else:
                raise ValueError(f'不支持的校验: {method}')
            if len(data) != length:
                raise ValueError('校验结果长度不匹配')
            put_bytes(start, data, expect_len=length)
        return bytes(frame)

    def crc16(self, data: bytes, poly: int = 0x1021, init_val: int = 0xFFFF) -> int:
        crc = init_val
        for b in data:
            crc ^= (b << 8)
            for _ in range(8):
                if crc & 0x8000:
                    crc = (crc << 1) ^ poly
                else:
                    crc <<= 1
                crc &= 0xFFFF
        return crc

    def edit_button(self, button):
        """编辑按钮（支持普通/自定义两种模式）"""
        button_info = button.button_info.copy()
        if button_info.get('custom_mode'):
            edited_info = self.show_custom_button_dialog(button_info)
        else:
            edited_info = self.show_button_dialog(button_info)
        if edited_info:
            # 更新按钮信息
            button.button_info = edited_info
            button.setText(edited_info['name'])
            button.setGeometry(edited_info['x'], edited_info['y'], edited_info['width'], edited_info['height'])
            # 更新/创建输入框
            if edited_info.get('has_input'):
                if getattr(button, 'button_input', None) is None:
                    input_edit = QLineEdit(self.button_container)
                    button.button_input = input_edit
                button.button_input.setPlaceholderText(edited_info.get('input_label', '输入数据'))
                button.button_input.setGeometry(
                    edited_info['x'], edited_info['y'] + edited_info['height'] + 4,
                    edited_info['width'], 28
                )
                button.button_input.show()
            else:
                if getattr(button, 'button_input', None) is not None:
                    button.button_input.deleteLater()
                    button.button_input = None

    def on_container_click(self, event):
        """容器点击事件 - 用于添加按钮到点击位置"""
        if event.button() == Qt.LeftButton:  # 左键点击
            # 可以在这里添加在点击位置创建按钮的功能
            pass
            
    def save_device_config(self):
        """保存设备控制配置"""
        if not self.control_buttons:
            self.append_data_display("没有按钮配置可保存", "yellow")
            return
            
        try:
            file_path, _ = QFileDialog.getSaveFileName(
                self, "保存设备控制配置", "", "JSON文件 (*.json)"
            )
            
            if file_path:
                config_data = []
                for button in self.control_buttons:
                    config_data.append(button.button_info)
                    
                import json
                with open(file_path, 'w', encoding='utf-8') as f:
                    json.dump(config_data, f, ensure_ascii=False, indent=2)
                    
                self.append_data_display(f"设备控制配置已保存到: {file_path}", "green")
                
        except Exception as e:
            self.append_data_display(f"保存配置失败: {str(e)}", "red")
            
    def load_device_config(self):
        """加载设备控制配置"""
        try:
            file_path, _ = QFileDialog.getOpenFileName(
                self, "加载设备控制配置", "", "JSON文件 (*.json)"
            )
            
            if file_path:
                import json
                with open(file_path, 'r', encoding='utf-8') as f:
                    config_data = json.load(f)
                    
                # 清空现有按钮
                self.clear_all_buttons()
                
                # 创建新按钮
                for button_info in config_data:
                    self.create_control_button(button_info)
                    
                self.append_data_display(f"设备控制配置已从 {file_path} 加载", "green")
                
        except Exception as e:
            self.append_data_display(f"加载配置失败: {str(e)}", "red")
            
    def clear_all_buttons(self):
        """清空所有按钮"""
        for button in self.control_buttons:
            button.deleteLater()
        self.control_buttons.clear()


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main() 